﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;
using System.Text.RegularExpressions;

namespace JeanFish.Conversion
{
    public class JSON
    {
        static Parser parser;
        static JSON()
        {
            parser = new Parser();
        }
        public static object Parse(string jsonStr)
        {
            return parser.Parse(new StringReader(jsonStr));
        }
        public static object Parse(Stream jsonStr)
        {
            return parser.Parse(new StreamReader(jsonStr));

        }
        public static object Parse(TextReader jsonStr)
        {
            return parser.Parse(jsonStr);

        }
        private class Parser
        {
            #region These are the six structural characters:
            /// <summary>
            /// begin-array = ws %x5B ws  ; [ left square bracket
            /// </summary>
            const char BeginArray = '[';

            /// <summary>
            /// begin-object = ws %x7B ws  ; { left curly bracket
            /// </summary>
            const char BeginObject = '{';

            /// <summary>
            /// end-array = ws %x5D ws  ; ] right square bracket
            ///</summary>
            const char EndArray = ']';

            /// <summary>
            /// end-object = ws %x7D ws  ; } right curly bracket
            /// </summary>
            const char EndObject = '}';

            /// <summary>
            /// name-separator = ws %x3A ws  ; : colon
            /// </summary>   
            const char NameSeparator = ':';

            /// <summary>
            /// value-separator = ws %x2C ws  ; , comma
            /// </summary>
            const char ValueSeparator = ',';

            /// <summary>
            /// ws
            /// </summary>
            #endregion
            #region  Insignificant whitespace is allowed before or after any of the six structural characters.
            //ws = *(
            /// <summary>
            /// %x20 /              ; Space
            /// </summary>
            const char SPACE = ' ';
            /// <summary>
            /// %x09 /              ; Horizontal tab
            /// </summary>
            const char TAB = '\t';
            /// <summary>
            /// %x0A /              ; Line feed or New line
            /// </summary>
            const char LF = '\n';
            /// <summary>
            /// %x0D                ; Carriage return
            /// </summary>
            const char CR = '\r';
            char[] WS = { SPACE, TAB, LF, CR };

            #endregion
            #region The literal names MUST be lowercase.  No other literal names are allowed.
            /// <summary>
            /// false = %x66.61.6c.73.65   ; false
            /// </summary>
            const string FALSE = "false";
            const char FALSE0 = 'f';
            /// <summary>
            /// null  = %x6e.75.6c.6c      ; null
            /// </summary>
            const string NULL = "null";
            const char NULL0 = 'n';
            /// <summary>
            /// true  = %x74.72.75.65      ; true
            /// </summary>
            const string TRUE = "true";
            const char TRUE0 = 't';
            #endregion
            #region Numbers
            /// <summary>
            /// decimal-point = %x2E       ; .
            /// </summary>
            const char DECIMALPOINT = '.';
            /// <summary>
            /// digit1-9 = %x31-39         ; 1-9
            /// </summary>
            char[] DIGIT1TO9 ={'1','2','3',
                              '4','5','6',
                              '7','8','9'};
            /// <summary>
            /// e = %x65 / %x45            ; e E
            /// </summary>
            const char e = 'e';
            /// <summary>
            /// e = %x65 / %x45            ; e E
            /// </summary>
            const char E = 'E';
            /// <summary>
            /// minus = %x2D               ; -
            /// </summary>
            const char MINUS = '-';
            /// <summary>
            /// plus = %x2B                ; +
            /// </summary>
            const char PLUS = '+';
            /// <summary>
            /// zero = %x30                ; 0
            /// </summary>
            const char ZERO = '0';
            #endregion
            #region String
            const char ESCAPE_QUOTATIONMARK = '"';
            const char ESCAPE_REVERSESOLIDUS = '\\';
            const char REVERSESOLIDUS = '\\';
            const char ESCAPE_SOLIDUS = '/';
            const char SOLIDUS='/';
            const char ESCAPE_BACKSPACE = 'b';
            const char BACKSPACE = '\b';
            const char ESCAPE_FORMFEED = 'f';
            const char FORMFEED = '\f';
            const char ESCAPE_LINEFEED = 'n';
            const char ESCAPE_CARRIAGERETURN = 'r';
            const char ESCAPE_TAB = 't';
            const char ESCAPE_UNICODE = 'u';
            const char ESCAPE = '\\';
            const char QUOTATIONMARK = '"';

            const char HEXDIG_0 = '0';
            const char HEXDIG_1 = '1';
            const char HEXDIG_2 = '2';
            const char HEXDIG_3 = '3';
            const char HEXDIG_4 = '4';
            const char HEXDIG_5 = '5';
            const char HEXDIG_6 = '6';
            const char HEXDIG_7 = '7';
            const char HEXDIG_8 = '8';
            const char HEXDIG_9 = '9';
            const char HEXDIG_a = 'a';
            const char HEXDIG_b = 'b';
            const char HEXDIG_c = 'c';
            const char HEXDIG_d = 'd';
            const char HEXDIG_e = 'e';
            const char HEXDIG_f = 'f';
            const char HEXDIG_A = 'A';
            const char HEXDIG_B = 'B';
            const char HEXDIG_C = 'C';
            const char HEXDIG_D = 'D';
            const char HEXDIG_E = 'E';
            const char HEXDIG_F = 'F';

            char[] HEXDIG = {HEXDIG_0,HEXDIG_1,HEXDIG_2,HEXDIG_3,
                            HEXDIG_4,HEXDIG_5,HEXDIG_6,HEXDIG_7,
                            HEXDIG_8,HEXDIG_9,HEXDIG_a,HEXDIG_A,
                            HEXDIG_b,HEXDIG_B,HEXDIG_c,HEXDIG_C,
                            HEXDIG_d,HEXDIG_D,HEXDIG_e,HEXDIG_E,
                            HEXDIG_f,HEXDIG_F};
            #endregion
            TextReader reader = null;
            char c;
            private char Next(string errorMessage = "")
            {
                if (string.IsNullOrEmpty(errorMessage))
                    errorMessage = Resources.JSON_UnexceptedEndOfText;
                int i = reader.Read();
                if (i >= 0) return (char)i;
                throw new Exception(errorMessage);
            }
            public Parser()
            {
                this.reader = null;
            }
            public object Parse(TextReader reader)
            {
                this.reader = reader;
                int i = -1;
                object obj = null;
                while ((i = reader.Read()) >= 0)
                {
                    c = (char)i;
                    switch (c)
                    {
                        case BeginObject:
                            obj= ParseObject();
                            break;
                        case BeginArray:
                            obj= ParseArray();
                            break;
                        case SPACE:
                        case TAB:
                        case CR:
                        case LF:
                            continue;
                        default:
                            throw new Exception(string.Format(Resources.JSON_UnexceptedStartOfText,c));
                    }
                }
                return obj;
                throw new Exception("error @ Parse.");
            }
            private JSONObject ParseObject()
            {
                JSONObject obj = new JSONObject();
                string name = string.Empty;
                object value;
                while (c != EndObject)
                {
                    c=Next();
                    switch (c)
                    {
                        case EndObject:
                            c = (char)reader.Read();
                            return obj;
                        case QUOTATIONMARK:
                            name =ParseName();
                            break;
                        case NameSeparator:
                            c = Next();
                            value=ParseValue(false);
                            obj[name] = value;

                            break;
                        case SPACE:
                        case TAB:
                        case CR:
                        case LF:
                        case ValueSeparator:
                            continue;
                        default:
                            throw new Exception("error @ ParseObject.");
                    }
                }
                c = (char)reader.Read();
                return obj;
            }
            private string ParseName()
            {
                return ParseString();
            }
            private object ParseValue(bool isInArray)
            {
                
                StringBuilder literalName = new StringBuilder();
                StringBuilder numberBuilder = new StringBuilder();
                string expected=string.Empty;
                if (c == EndArray || c == EndObject)
                {
                    throw new Exception("error @ ParseValue");
                }
                char valueEnd = EndObject;
                if (isInArray) valueEnd = EndArray;
                while (c!=ValueSeparator&&c!=valueEnd)
                {
                    if (literalName.Length > 0)
                    {
                        literalName.Append(c);
                        c = Next();
                        continue;
                    }
                    if (numberBuilder.Length > 0)
                    {
                        numberBuilder.Append(c);
                        c = Next();
                        continue;
                    }
                    switch (c)
                    {
                        case NULL0:
                            expected=NULL;
                            literalName.Append(c);
                            break;
                        case TRUE0:
                            expected=TRUE;
                            literalName.Append(c);
                            break;
                        case FALSE0:   
                            expected=FALSE;
                            literalName.Append(c);
                            break;
                        case BeginArray:
                            return ParseArray();
                        case BeginObject:
                            return ParseObject();
                        case QUOTATIONMARK:
                            return ParseString();
                        case SPACE:
                        case TAB:
                        case CR:
                        case LF:
                            break;
                        default:
                            numberBuilder.Append(c);
                            break;
                    }
                    c = Next();
                }
                if (literalName.Length > 0 && literalName.ToString().Trim() == expected)
                {
                    switch (expected)
                    {
                        case NULL:
                            return null;
                        case TRUE:
                            return true;
                        case FALSE:
                            return false;
                        default:
                            throw new Exception("error @ ParseValue");
                    }
                }
                if (numberBuilder.Length > 0)
                    return ParseNumber(numberBuilder.ToString());
                throw new Exception("no value error @ ParseValue");
            }
            private JSONArray ParseArray() 
            {
                JSONArray array = new JSONArray();
                while (c!= EndArray)
                {
                    c = Next();
                    switch (c)
                    {
                        case EndArray:
                            c = (char)reader.Read();
                            return array;
                        case SPACE:
                        case TAB:
                        case CR:
                        case LF:
                            continue;
                        case ValueSeparator:
                            c = Next();
                            array.Add(ParseValue(true));
                            break;
                        default:
                            array.Add(ParseValue(true));
                            break;
                    }
                }
                c = (char)reader.Read();
                return array;
            }
            private string ParseString()
            {
                StringBuilder str = new StringBuilder();
                char pre;
                while ((c = Next()) != QUOTATIONMARK)
                {
                    switch (c)
                    {
                        case ESCAPE:
                            pre = Next();
                            switch (pre)
                            {
                                case ESCAPE_QUOTATIONMARK:
                                    str.Append(QUOTATIONMARK);
                                    break;
                                case ESCAPE_REVERSESOLIDUS:
                                    str.Append(REVERSESOLIDUS);
                                    break;
                                case ESCAPE_SOLIDUS:
                                    str.Append(SOLIDUS);
                                    break;
                                case ESCAPE_BACKSPACE:
                                    str.Append(BACKSPACE);
                                    break;
                                case ESCAPE_FORMFEED:
                                    str.Append(FORMFEED);
                                    break;
                                case ESCAPE_LINEFEED:
                                    str.Append(LF);
                                    break;
                                case ESCAPE_CARRIAGERETURN:
                                    str.Append(CR);
                                    break;
                                case ESCAPE_TAB:
                                    str.Append(TAB);
                                    break;
                                case ESCAPE_UNICODE:
                                    str.Append(ParseUnicode());
                                    break;
                                default:
                                    str.Append(c);
                                    break;
                            }
                            break;
                        default:
                            str.Append(c);
                            break;
                    }
                }
                return str.ToString();
            }
            private char ParseUnicode()
            {
                StringBuilder unicodeBuilder = new StringBuilder();
                while (unicodeBuilder.Length < 4)
                {
                    c = Next();
                    switch (c)
                    {
                        case HEXDIG_0:
                        case HEXDIG_1:
                        case HEXDIG_2:
                        case HEXDIG_3:
                        case HEXDIG_4:
                        case HEXDIG_5:
                        case HEXDIG_6:
                        case HEXDIG_7:
                        case HEXDIG_8:
                        case HEXDIG_9:
                        case HEXDIG_a:
                        case HEXDIG_A:
                        case HEXDIG_b:
                        case HEXDIG_B:
                        case HEXDIG_c:
                        case HEXDIG_C:
                        case HEXDIG_d:
                        case HEXDIG_D:
                        case HEXDIG_e:
                        case HEXDIG_E:
                        case HEXDIG_f:
                        case HEXDIG_F:
                            unicodeBuilder.Append(c);
                            break;
                        default:
                            throw new Exception("error @ ParseUnicode.");
                    }
                }
                string unicodeStr=unicodeBuilder.ToString();
                byte[] bytes = new byte[2];
                bytes[1] = byte.Parse(int.Parse(unicodeStr.Substring(0, 2), NumberStyles.HexNumber).ToString());
                bytes[0] = byte.Parse(int.Parse(unicodeStr.Substring(2, 2), NumberStyles.HexNumber).ToString());
                return Encoding.Unicode.GetChars(bytes)[0];
            }
            static Regex NumberReg = new Regex(@"^(\-)?(0|[1-9][0-9]*)(.[0-9]+)?([eE][\-\+]?[0-9]+)?$");
            private double ParseNumber(string numberString)
            {
                numberString = numberString.Trim();
                if (NumberReg.IsMatch(numberString))
                {
                    try
                    {
                        return double.Parse(numberString);
                    }
                    catch(FormatException)
                    {
                        throw new Exception("error not number @ ParseNumber");
                    }
                }
                else
                    throw new Exception("error format @ ParseNumber");
            }
        }
    }
}
